﻿using System;
using System.Collections.Generic;
using System.Data;
using Conference.Domain;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
using Dapper;
using DotNetCore.CAP;
using Newtonsoft.Json;
using Polly;
using System.Data.SqlClient;
using System.IO;
using Conference.Common.Log;
using System.Collections.Concurrent;

namespace Conference.Common
{
    public class PublishDomainEventService : IPublishDomainEventService
    {
        private readonly ICapPublisher _capPublisher;
        private readonly ILoggerHelper _loggerHelper;
        private readonly string ConnectionStr = "Data Source=DESKTOP-G8UTIT6\\LI;Initial Catalog=Conference;User ID=sa;Password=123456li;Max Pool Size = 512;MultipleActiveResultSets=true";
        public PublishDomainEventService(ICapPublisher capPublisher, ILoggerHelper loggerHelper)
        {
            _capPublisher = capPublisher;
            _loggerHelper = loggerHelper;
        }

        /// <summary>
        /// 发布事件，储存事件
        /// </summary>
        /// <typeparam name="TAggregationRoot"></typeparam>
        /// <param name="event"></param>
        public void PublishEvent<TAggregationRoot>(TAggregationRoot @event) where TAggregationRoot : IAggregationRoot
        {
            try
            {
                var domainEventList = @event.UncommittedEvents.ToList();
                if (domainEventList.Count == 0)
                {
                    throw new Exception("请添加事件!");
                }

                using (var connection = new SqlConnection(ConnectionStr))
                {
                    if (connection.State == ConnectionState.Closed)
                    {
                        connection.Open();
                    }

                    using (var transaction = connection.BeginTransaction(_capPublisher, autoCommit: false))
                    {
                        try
                        {
                            if (domainEventList.Count > 0)
                            {
                                foreach (var domainEvent in domainEventList)
                                {
                                    EventStorage eventStorage = new EventStorage
                                    {
                                        Id = Guid.NewGuid(),
                                        AggregateRootId = domainEvent.AggregateRootId,
                                        AggregateRootType = domainEvent.AggregateRootType,
                                        CreateDateTime = domainEvent.CreateDateTime,
                                        Version = domainEvent.Version,
                                        EventData = JsonConvert.SerializeObject(domainEvent)
                                    };
                                    var eventStorageSql =
                                        $"INSERT INTO EventStorageInfo(Id,AggregateRootId,AggregateRootType,CreateDateTime,Version,EventData) VALUES (@Id,@AggregateRootId,@AggregateRootType,@CreateDateTime,@Version,@EventData)";
                                    connection.Execute(eventStorageSql, eventStorage, transaction);
                                    _capPublisher.Publish(domainEvent.GetRoutingKey(), domainEvent);
                                }
                            }

                            transaction.Commit();
                            transaction.Dispose();
                            connection.Close();
                            @event.ClearEvents();
                        }
                        catch (Exception e)
                        {
                            transaction.Rollback();
                            throw;
                        }
                    }
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        /// <summary>
        /// 异步发布事件，储存事件
        /// </summary>
        /// <typeparam name="TAggregationRoot"></typeparam>
        /// <param name="event"></param>
        /// <returns></returns>
        public async Task PublishEventAsync<TAggregationRoot>(TAggregationRoot @event)
            where TAggregationRoot : IAggregationRoot
        {
            try
            {
                var domainEventList = @event.UncommittedEvents.ToList();
                if (domainEventList.Count == 0)
                {
                    throw new Exception("请添加事件!");
                }

                using (var connection = new SqlConnection(ConnectionStr))
                {
                    if (connection.State == ConnectionState.Closed)
                    {
                        connection.Open();
                    }

                    using (var transaction = connection.BeginTransaction(_capPublisher, autoCommit: false))
                    {
                        try
                        {
                            if (domainEventList.Count > 0)
                            {
                                foreach (var domainEvent in domainEventList)
                                {
                                    EventStorage eventStorage = new EventStorage
                                    {
                                        Id = Guid.NewGuid(),
                                        AggregateRootId = domainEvent.AggregateRootId,
                                        AggregateRootType = domainEvent.AggregateRootType,
                                        CreateDateTime = domainEvent.CreateDateTime,
                                        Version = domainEvent.Version,
                                        EventData = JsonConvert.SerializeObject(domainEvent)
                                    };
                                    var eventStorageSql =
                                        $"INSERT INTO EventStorageInfo(Id,AggregateRootId,AggregateRootType,CreateDateTime,Version,EventData) VALUES (@Id,@AggregateRootId,@AggregateRootType,@CreateDateTime,@Version,@EventData)";
                                    await connection.ExecuteAsync(eventStorageSql, eventStorage, transaction)
                                        .ConfigureAwait(false);
                                    await _capPublisher.PublishAsync(domainEvent.GetRoutingKey(), domainEvent)
                                        .ConfigureAwait(false);
                                }
                            }

                            transaction.Commit();
                            transaction.Dispose();
                            connection.Close();
                            @event.ClearEvents();
                        }
                        catch (Exception e)
                        {
                            transaction.Rollback();
                            throw;
                        }
                    }
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        /// <summary>
        /// 存储聚合根中的事件到EventStorage 发布事件
        /// </summary>
        /// <typeparam name="TAggregationRoot"></typeparam>
        /// <param name="event"></param>
        /// <returns></returns>
        public async Task AppendEventStoragePublishEventAsync<TAggregationRoot>(TAggregationRoot @event)
            where TAggregationRoot : IAggregationRoot
        {
            var domainEventList = @event.UncommittedEvents.ToList();
            if (domainEventList.Count == 0)
            {
                throw new Exception("请添加事件!");
            }

            await TryAppendEventStorageAsync(domainEventList).ContinueWith(async e =>
            {
                if (e.Result == (int)EventStorageStatus.Success)
                {
                    await TryPublishDomainEventAsync(domainEventList).ConfigureAwait(false);
                    @event.ClearEvents();
                }
            });
        }

        /// <summary>
        /// 发布领域事件
        /// </summary>
        /// <returns></returns>
        public async Task PublishDomainEventAsync(List<IDomainEvent> domainEventList)
        {
            using (var connection =
                new SqlConnection(ConnectionStr))
            {
                if (connection.State == ConnectionState.Closed)
                {
                    await connection.OpenAsync().ConfigureAwait(false);
                }
                using (var transaction = await connection.BeginTransactionAsync().ConfigureAwait(false))
                {
                    try
                    {
                        if (domainEventList.Count > 0)
                        {
                            foreach (var domainEvent in domainEventList)
                            {
                                await _capPublisher.PublishAsync(domainEvent.GetRoutingKey(), domainEvent).ConfigureAwait(false);
                            }
                        }
                        await transaction.CommitAsync().ConfigureAwait(false);
                    }
                    catch (Exception e)
                    {
                        await transaction.RollbackAsync().ConfigureAwait(false);
                        throw;
                    }
                }
            }
        }

        /// <summary>
        /// 发布领域事件重试
        /// </summary>
        /// <param name="domainEventList"></param>
        /// <returns></returns>
        public async Task TryPublishDomainEventAsync(List<IDomainEvent> domainEventList)
        {
            var policy = Policy.Handle<SocketException>().Or<IOException>().Or<Exception>()
                .RetryForeverAsync(onRetry: exception =>
                {
                    Task.Factory.StartNew(() =>
                    {
                        //记录重试的信息
                        _loggerHelper.LogInfo("发布领域事件异常", exception.Message);
                    });
                });
            await policy.ExecuteAsync(async () =>
            {
                await PublishDomainEventAsync(domainEventList).ConfigureAwait(false);
            });

        }

        /// <summary>
        /// 存储聚合根中的事件到EventStorage中
        /// </summary>
        /// <returns></returns>
        public async Task<int> AppendEventStorageAsync(List<IDomainEvent> domainEventList)
        {
            if (domainEventList.Count == 0)
            {
                throw new Exception("请添加事件!");
            }
            var status = (int)EventStorageStatus.Failure;
            using (var connection = new SqlConnection(ConnectionStr))
            {
                try
                {
                    if (connection.State == ConnectionState.Closed)
                    {
                        await connection.OpenAsync().ConfigureAwait(false);
                    }
                    using (var transaction = await connection.BeginTransactionAsync().ConfigureAwait(false))
                    {
                        try
                        {
                            if (domainEventList.Count > 0)
                            {
                                foreach (var domainEvent in domainEventList)
                                {
                                    EventStorage eventStorage = new EventStorage
                                    {
                                        Id = Guid.NewGuid(),
                                        AggregateRootId = domainEvent.AggregateRootId,
                                        AggregateRootType = domainEvent.AggregateRootType,
                                        CreateDateTime = domainEvent.CreateDateTime,
                                        Version = domainEvent.Version,
                                        EventData = Events(domainEvent)
                                    };
                                    var eventStorageSql =
                                        $"INSERT INTO EventStorageInfo(Id,AggregateRootId,AggregateRootType,CreateDateTime,Version,EventData) VALUES (@Id,@AggregateRootId,@AggregateRootType,@CreateDateTime,@Version,@EventData)";
                                    await connection.ExecuteAsync(eventStorageSql, eventStorage, transaction).ConfigureAwait(false);
                                }
                            }
                            await transaction.CommitAsync().ConfigureAwait(false);
                            status = (int)EventStorageStatus.Success;
                        }
                        catch (Exception e)
                        {
                            await transaction.RollbackAsync().ConfigureAwait(false);
                            throw;
                        }
                    }

                }
                catch (Exception e)
                {
                    connection.Close();
                    throw;
                }
            }
            return status;
        }

        /// <summary>
        /// AppendEventStorageAsync异常重试
        /// </summary>
        public async Task<int> TryAppendEventStorageAsync(List<IDomainEvent> domainEventList)
        {
            var policy = Policy.Handle<SocketException>().Or<IOException>().Or<Exception>()
                .RetryForeverAsync(onRetry: exception =>
                {
                    Task.Factory.StartNew(() =>
                    {
                        //记录重试的信息
                        _loggerHelper.LogInfo("存储事件异常", exception.Message);
                    });
                });
            var result = await policy.ExecuteAsync(async () =>
              {
                  var resulted = await AppendEventStorageAsync(domainEventList).ConfigureAwait(false);
                  return resulted;
              });
            return result;
        }

        /// <summary>
        /// 根据DomainEvent序列化事件Json
        /// </summary>
        /// <param name="domainEvent"></param>
        /// <returns></returns>
        public string Events(IDomainEvent domainEvent)
        {
            ConcurrentDictionary<string, string> dictionary = new ConcurrentDictionary<string, string>();
            //获取领域事件的类型（方便解析Json）
            var domainEventTypeName = domainEvent.GetType().Name;
            var domainEventStr = JsonConvert.SerializeObject(domainEvent);
            dictionary.GetOrAdd(domainEventTypeName, domainEventStr);
            var eventData = JsonConvert.SerializeObject(dictionary);
            return eventData;
        }

    }

    public enum EventStorageStatus
    {
        /// <summary>
        /// 成功
        /// </summary>
        Success = 0,
        /// <summary>
        /// 失败
        /// </summary>
        Failure = 1,
        /// <summary>
        /// 重复
        /// </summary>
        Repeat = 2,

    }
}
